<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        * {
            padding: 0;
            margin: 0;
            line-height: 1.2;
            font-size: 12px;
            list-style: none;
        }

        html, body {
            height: 100%;
            background: #f9f9f9;
            position: relative;
        }

        .container {
            width: 800px;
            height: 600px;
            position: absolute;
            left: 0;
            top: 0;
            right: 0;
            bottom: 0;
            margin: auto;
            background-color: #fff;
            box-shadow: 0 0 6px 3px rgba(0, 0, 0, .6);
            border-radius: 4px;
            text-align: center;
        }
    </style>
</head>
<body>
    <div class="container">
        文件：<input type="file" ><br>
        <br><br><br><br><br>
        <button id="btn">点击提交</button>
        <br>
        <br>
        <br>
        文件总大小：<span id="total-size"></span><br>
        已上传大小：<span id="uploaded-size"></span><br>
        已上传进度：<span id="uploaded-progress"></span> <span id="deal-upload" style="display: none;">处理中...</span>
    </div>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.0.0/jquery.min.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/axios/0.19.2/axios.min.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/blueimp-md5/2.16.0/js/md5.min.js"></script>
    <script>
        !function() {
            var btn = document.getElementById('btn');
            var ajax = axios.create();
            var fileName = '';
            var totalSize = 0;
            var eachChunkUpload = [];

            // $.post('/test/api', {
            //     username: 'fywang',
            //     password: '123456'
            // }).done(function(res) {
            //     console.log(res.data);
            // }).always([function() {
            //     console.log(1)
            // }, function() {
            //     console.log(2)
            // }]);

            var req = new Req({
                type: 'post',
                url: '/test/api',
                data: {
                    username: 'fywang',
                    password: '123456'
                }
            });

            req.open()

            /**
             * slice函数 按一定大小对文件进行分片，返回分片的数组
             * File 接口基于Blob，继承了 blob 的功能并将其扩展使其支持用户系统上的文件。
             * */
            function slice(file, piece) {
                var totalSize = file.size,
                    start = 0,
                    end = start + piece,
                    chunks = [];

                while(start < totalSize) {
                    var blob = file.slice(start, end);
                    chunks.push(blob);
                    start = end;
                    end = start + piece;
                }

                return chunks;
            }
            /**
             * 因为axios的onUploadProgress函数返回的是每一个分片已上传的大小，所以需要使用闭包的方式将每一个分片已上传的保存在一个数组中
             * 然后在onUploadProgress中进行计算，可以得出已上传的大小与百分比
             * */
            function progressCloure(index) {
                return function(progressEvent) {
                    eachChunkUpload[index] = progressEvent.loaded;
                    var uploadedSize = eachChunkUpload.reduce((tt, item) => tt + item, 0);
                    var percent = `${(100 * uploadedSize / totalSize).toFixed(2)}%`;
                    document.getElementById('uploaded-size').innerText = (uploadedSize / (1024 * 1024)).toFixed(2) + 'mb';
                    document.getElementById('uploaded-progress').innerText = percent;

                }
            }

            /**
             * 使用函数的递归调用来上传每一个分片（递归调用可控制切片太多时请求的并发数），使用MD5加密的方式来确保相同文件的的md5值相同
             * 在上传到的同时，同时携带有文件名的MD5的值与片段的下标，以确保后台在拼接文件片段时的顺序
             * */
            function uploadFile(index, chunks) {
                var formData = new FormData();
                var md5name = md5(fileName)+'_'+index;
                formData.append('file', chunks[index]);
                formData.append('name', md5name);
                if (index > chunks.length - 1)
                    return mkfile();
                else
                    ajax({
                        method: "POST",
                        url: "/upload",
                        data: formData,
                        onUploadProgress: progressCloure(index)
                    }).then(res => {
                        uploadFile(index + 1, chunks);
                    })
            }

            /**
             * 通知后端，前端的文件分片以上传完成，服务器可以合并分片了
             */
            function mkfile() {
                var md5name = md5(fileName);
                document.getElementById('deal-upload').style.display = 'inline';
                ajax({
                    method: "POST",
                    url: "/mkfile",
                    data: {
                        name: md5name,
                        filename: fileName
                    }
                }).then(res => {
                    document.getElementById('deal-upload').innerText = '处理完毕';
                })
            }

            function Req(options) {
                this.xmlHttp = null;
                if (window.XMLHttpRequest) {
                    this.xmlHttp = new XMLHttpRequest();
                } else if (window.ActiveXObject) {
                    this.xmlHttp = new ActiveXObject('Microsoft.XMLHTTP')
                }

                if (!this.xmlHttp) {
                    throw Error('you browser does not support XMLHTTP')
                }
                this.open = function() {
                    this.xmlHttp.open(options.type, options.url);
                    if (options.type === 'post') {
                        if (typeof options.data === 'object') {
                            this.xmlHttp.setRequestHeader('content-type', 'application/json');
                            this.xmlHttp.send(JSON.stringify(options.data));
                        } else {
                            this.xmlHttp.setRequestHeader('content-type', 'application/x-www-form-urlenconded');
                            this.xmlHttp.send(options.data);
                        }

                    } else {
                        this.xmlHttp.send();
                    }

                    this.xmlHttp.onreadystatechange = function() {
                        console.log(this)
                        if (this.readyState === 4 && this.status === 200) {
                            console.log(this.responseText);
                        }
                    }
                }
            }

            btn.onclick = function() {
                var file = document.querySelector('input[type=file]').files[0];
                var chunks = slice(file, 1024 * 1024 * 50);
                totalSize = file.size;
                document.getElementById('total-size').innerText = (totalSize / (1024 * 1024)).toFixed(2) + 'mb';
                fileName = file.name;
                eachChunkUpload = [];
                uploadFile(0, chunks);
            }

        }()
    </script>
</body>
</html>